// Background service worker for 日程推推
// Handles schedule storage, chrome.alarms scheduling, and notifications

const STORAGE_KEY = "schedules"

function getNowMs() {
  return Date.now()
}

function ymd(date) {
  const y = date.getFullYear()
  const m = String(date.getMonth() + 1).padStart(2, "0")
  const d = String(date.getDate()).padStart(2, "0")
  return `${y}-${m}-${d}`
}

async function readAllSchedules() {
  return new Promise((resolve) => {
    try {
      chrome.storage?.local?.get([STORAGE_KEY], (res) => {
        resolve(Array.isArray(res?.[STORAGE_KEY]) ? res[STORAGE_KEY] : [])
      })
    } catch (_e) {
      resolve([])
    }
  })
}

async function writeAllSchedules(schedules) {
  return new Promise((resolve) => {
    try {
      chrome.storage?.local?.set({ [STORAGE_KEY]: schedules || [] }, resolve)
    } catch (_e) {
      resolve()
    }
  })
}

function alarmNameFor(id) {
  return `schedule:${id}`
}

function parseTimeToDate(dateStr, timeStr) {
  // dateStr: YYYY-MM-DD, timeStr: HH:mm
  const [y, m, d] = dateStr.split("-").map((s) => parseInt(s, 10))
  const [hh, mm] = timeStr.split(":").map((s) => parseInt(s, 10))
  const dt = new Date(y, (m || 1) - 1, d || 1, hh || 0, mm || 0, 0, 0)
  return dt
}

function computeNextTriggerMs(schedule, fromMs) {
  const { date, time, repeat } = schedule
  const from = new Date(fromMs || getNowMs())
  let target = parseTimeToDate(date, time)

  if (repeat === "none" || !repeat) {
    return target.getTime() >= from.getTime() ? target.getTime() : null
  }

  // If target already passed relative to from, roll forward according to repeat
  while (target.getTime() < from.getTime()) {
    if (repeat === "daily") {
      target.setDate(target.getDate() + 1)
    } else if (repeat === "weekly") {
      target.setDate(target.getDate() + 7)
    } else if (repeat === "monthly") {
      const day = target.getDate()
      target.setMonth(target.getMonth() + 1)
      // keep day if possible (Date will auto-adjust for shorter months)
      if (target.getDate() !== day) {
        // best-effort: already adjusted by Date
      }
    } else {
      break
    }
  }
  return target.getTime()
}

async function scheduleAlarmFor(schedule) {
  const nextMs = computeNextTriggerMs(schedule)
  const name = alarmNameFor(schedule.id)
  try {
    chrome.alarms.clear(name)
  } catch (_e) {}
  if (nextMs) {
    chrome.alarms.create(name, { when: nextMs })
  }
}

async function rescheduleAll() {
  const schedules = await readAllSchedules()
  for (const s of schedules) {
    if (s.enabled !== false) {
      await scheduleAlarmFor(s)
    }
  }
}

function createId() {
  try {
    // prefer crypto if available
    const arr = new Uint32Array(2)
    crypto.getRandomValues(arr)
    return `${Date.now()}-${arr[0].toString(16)}${arr[1].toString(16)}`
  } catch (_e) {
    return `${Date.now()}-${Math.random().toString(16).slice(2)}`
  }
}

async function addSchedule(data) {
  const schedules = await readAllSchedules()
  const schedule = {
    id: createId(),
    title: data.title || "",
    date: data.date, // YYYY-MM-DD
    time: data.time, // HH:mm
    repeat: data.repeat || "none",
    createdAt: Date.now(),
    enabled: true
  }
  schedules.push(schedule)
  await writeAllSchedules(schedules)
  await scheduleAlarmFor(schedule)
  return schedule
}

async function updateSchedule(id, partial) {
  const schedules = await readAllSchedules()
  const idx = schedules.findIndex((s) => s.id === id)
  if (idx === -1) return null
  const next = { ...schedules[idx], ...partial }
  schedules[idx] = next
  await writeAllSchedules(schedules)
  await scheduleAlarmFor(next)
  return next
}

async function deleteSchedule(id) {
  const schedules = await readAllSchedules()
  const next = schedules.filter((s) => s.id !== id)
  await writeAllSchedules(next)
  try {
    await chrome.alarms.clear(alarmNameFor(id))
  } catch (_e) {}
  return true
}

function inMonth(dateStr, year, monthIndex0) {
  const [y, m] = dateStr.split("-").map((s) => parseInt(s, 10))
  return y === year && m - 1 === monthIndex0
}

chrome.runtime.onInstalled.addListener(() => {
  rescheduleAll()
})

chrome.runtime.onStartup?.addListener(() => {
  rescheduleAll()
})

chrome.alarms.onAlarm.addListener(async (alarm) => {
  if (!alarm?.name?.startsWith("schedule:")) return
  const id = alarm.name.split(":")[1]
  const schedules = await readAllSchedules()
  const schedule = schedules.find((s) => s.id === id)
  if (!schedule) return

  const title = `日程提醒 - ${schedule.title || "无标题"}`
  const message = `您有一个日程即将开始：${schedule.date} ${schedule.time}`
  try {
    const iconUrl = chrome.runtime?.getURL
      ? chrome.runtime.getURL("assets/icon.png")
      : "assets/icon.png"
    chrome.notifications.create(id, {
      type: "basic",
      iconUrl,
      title,
      message,
      buttons: [{ title: "查看详情" }, { title: "关闭提醒" }],
      priority: 2
    })
  } catch (_e) {
    // ignore
  }

  // Reschedule next occurrence if repeating
  if (schedule.repeat && schedule.repeat !== "none") {
    await scheduleAlarmFor(schedule)
  }
})

chrome.notifications.onButtonClicked.addListener(
  (notificationId, buttonIndex) => {
    if (!notificationId) return
    if (buttonIndex === 0) {
      // Open popup to the date
      try {
        chrome.action.openPopup?.()
      } catch (_e) {}
    }
    try {
      chrome.notifications.clear(notificationId)
    } catch (_e) {}
  }
)

chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
  const respond = (payload) => {
    try {
      sendResponse(payload)
    } catch (_e) {}
  }

  ;(async () => {
    try {
      switch (msg?.type) {
        case "addSchedule": {
          const created = await addSchedule(msg.payload)
          respond({ ok: true, data: created })
          break
        }
        case "updateSchedule": {
          const updated = await updateSchedule(msg.id, msg.payload)
          respond({ ok: !!updated, data: updated })
          break
        }
        case "deleteSchedule": {
          await deleteSchedule(msg.id)
          respond({ ok: true })
          break
        }
        case "listSchedulesByMonth": {
          const year = msg.year
          const monthIndex0 = msg.month // 0-based
          const all = await readAllSchedules()
          const data = all.filter((s) => inMonth(s.date, year, monthIndex0))
          respond({ ok: true, data })
          break
        }
        case "listAllSchedules": {
          const all = await readAllSchedules()
          respond({ ok: true, data: all })
          break
        }
        default:
          respond({ ok: false, error: "unknown_message" })
      }
    } catch (e) {
      respond({ ok: false, error: String(e?.message || e) })
    }
  })()

  return true // keep the message channel open for async response
})
